! 
! SYS_SIZE is the number of clicks (16 bytes) to be loaded.
! 0x3000 is 0x30000 bytes = 196kB, more than enough for current
! versions of linux
! =========================================================================
! SYS_SIZE 是需要被加载的节数（16字节为一节）
! 0x3000 等于 0x30000 字节，即 196kB。这对于当前版本的 Linux 来说足够了。
! =========================================================================
SYSSIZE = 0x3000
!
!	bootsect.s		(C) 1991 Linus Torvalds
!
! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
! iself out of the way to address 0x90000, and jumps there.
!
! It then loads 'setup' directly after itself (0x90200), and the system
! at 0x10000, using BIOS interrupts. 
!
! NOTE! currently system is at most 8*65536 bytes long. This should be no
! problem, even in the future. I want to keep it simple. This 512 kB
! kernel size should be enough, especially as this doesn't contain the
! buffer cache as in minix
!
! The loader has been made as simple as possible, and continuos
! read errors will result in a unbreakable loop. Reboot by hand. It
! loads pretty fast by getting whole sectors at a time whenever possible.
! ==========================================================================
! bootsect.s    （版权所有 1991 Linus Torvalds） 
! bootsect.s是由BIOS启动子程序加载到 0x7c00(31k)的位置，然后通过跳转指令转移到 0x90000 (576k) 地址处开始执行。
! 移动代码是为了让 0x7c00 这个内存位置空出来，方便后面加载的代码和系统使用。
! 
! 它然后使用BIOS中断，将'setup'直接加载到自己的后面 0x90200 (576.5k)位置，接着通过BIOS中断将位于 0x10000 位置的操作系统内核加载进来
! 
! 注意！当前系统最多只有 8*65536 字节长。这应该不成问题，即使在未来也是如此。我希望保持它的简单性。
! 这个 512kB 的内核大小应该足够了，尤其是因为它不像 Minix 那样包含缓冲区高速缓存。
! 
! bootsect.s 中的加载程序尽可能地简单，以便实现更高的可靠性和更快的加载速度。
! 如果读取存储设备中的扇区时发生错误，会进入一个无法中断的循环中，需要手动重启计算机。为了提高加载速度，bootsect.s 会尽可能地一次性读取整个扇区。
! 
! ==========================================================================

.globl begtext, begdata, begbss, endtext, enddata, endbss  ! 定义了6个全局标识符 这些标识符用来指示内核代码和数据的起始位置
.text
begtext:                                                   ! 指示代码段
.data
begdata:                                                   ! 指示数据段
.bss
begbss:                                                    ! 指示BSS段，也即未初始化的数据段
.text

SETUPLEN = 4				! nr of setup-sectors  设置程序的扇区数
BOOTSEG  = 0x07c0			! original address of boot-sector  指示引导扇区的原始位置
INITSEG  = 0x9000			! we move boot here - out of the way  将引导扇区移动到新的地址
SETUPSEG = 0x9020			! setup starts here  设置程序的起始位置
SYSSEG   = 0x1000			! system loaded at 0x10000 (65536).   指示内核系统将加载到的位置
ENDSEG   = SYSSEG + SYSSIZE		! where to stop loading  指示内核代码的结束位置

! ROOT_DEV:	0x000 - same type of floppy as boot.
!		0x301 - first partition on first drive etc
! =============================================================================
! 0x000 表示根文件系统位于与启动磁盘相同类型的软盘上，而 0x301 表示根文件系统位于第一块硬盘的第一个分区上。
! 其中 0x301的表示方法为：0x300 + 第一个硬盘编号 * 0x10 + 第一个分区编号。
! 因此，0x301 表示第一个硬盘（硬盘编号为 0x00）的第一个分区（分区编号为 0x01），这也是常见的默认设置
! 设备号=主设备号*256+次设备号  dev_no=(major<<8)+minor
! 0x300 代表整个第一硬盘
! 0x301 代表第一个盘第一个分区
! ...
! 0x305 代表整个第二硬盘
! 0x306 代表第二个盘第一个分区
! ...
! =============================================================================

ROOT_DEV = 0x306

! bootsect启动程序将自身512B内容从内存0x07C00复制到0x90000（INITSEG）处
entry start                                                 ! entry表示程序入口点， start是代码标识符，将从start位置开始执行代码
start:                                                      ! 这是汇编语法中的一个label，表示代码块开始
	mov	ax,#BOOTSEG                                         ! 将#BOOTSEG 加载到 ax 寄存器中，再加载到 dx 中
	mov	ds,ax                                               ! 其实就是 将数据段的基地址设置为引导扇区的地址。
	mov	ax,#INITSEG                                         ! 将#INITSEG 加载到 es 中
	mov	es,ax                                               ! 将附加段的基地址设置为新的初始化段地址。
	mov	cx,#256                                             ! 初始化 CX 寄存器为 256 将 cx 初始化为 256，是因为在 8086 处理器中，一次 rep movs 可以复制 cx 个字节的数据。由于 movw 指令复制的是字（2 个字节）数据，因此将 cx 初始化为 256，就相当于复制 256 * 2 = 512 个字节的数据，可以覆盖整个内存的前 512 个字节，这足以完成内存初始化的操作。
	sub	si,si                                               ! 初始化 SI 寄存器为 0
	sub	di,di                                               ! 初始化 DI 寄存器为 0
	rep                                                     ! 将源地址处的一个字（2 个字节）复制到目标地址处。rep 是重复前缀，用于控制指令重复执行的次数，cx 寄存器用于保存需要重复执行的次数。因此，执行 rep movw 指令会重复执行 cx 次复制操作，将 ds:si 指向的 2 字节数据复制到 es:di 指向的目标地址处，每次复制操作 si 和 di 寄存器都会自动加上 2，以便指向下一个源数据和目标数据。
	movw                                                    ! rep movw 用于将 ds:si 处的数据复制到 es:di 处，起到了清空一段内存的作用。具体来说，将 cx 初始化为 256，就相当于将这个操作重复执行 256 次，即复制了 256 * 2 = 512 个字节的数据，将内存的前 512 个字节清空。这个操作可以保证内存的初始状态都是一样的，避免了系统在启动时出现意料之外的问题。
	jmpi	go,INITSEG                                      ! 跳转到 go 标签所在的位置，即将 CS 寄存器设置为当前代码段的值，IP 寄存器设置为 INITSEG    CS:IP=0x90000:go代码的位置 指向当前正在执行的指令所在的内存地址

! 执行jmpi go,0x90000之前都是将bootsect启动内容复制到0x90000位置，并且将代码段寄存器CS指向到了0x90000的位置，当执行了行代码之后，程序就不需要依赖BIOS了
! 下面的go代码段的目的是对 数据段寄存器DS、扩展段寄存器ES、堆栈段寄存器SS都指向于0x90000，将栈顶指针寄存器SP指向0x9FF00
go:	mov	ax,cs                                               ! 将 CS 寄存器的值加载到 AX 寄存器中，即将 DS、ES 寄存器的值也设置为 CS 的值
	mov	ds,ax                                               ! 就是BOOTSEG所在的代码段的值，以确保在初始化内核时使用正确的段寄存器
	mov	es,ax
! put stack at 0x9ff00. 将0x9ff00压栈
	mov	ss,ax                                               ! 将SS寄存器设置为当前代码段DS的值，确保堆栈位于当前程序的数据段中，避免在操作堆栈时出现问题
	mov	sp,#0xFF00											! arbitrary value >>512 将栈指针(SP)设置到0x9ff00处，即将堆栈设置在内存中靠近顶部的位置。将sp设置为一个较小的位置给其他程序足够的空间 至此 SS:SP=0x90000:0x0ff00，堆栈物理地址-> 0x9ff00

! load the setup-sectors directly after the bootblock.  
! Note that 'es' is already set up.  
! =============================================================================
! 在引导块之后直接加载设置扇区。注意'es'寄存器已经设置完毕
! 下面的代码是将setup程序加载到内存中
! 加载setup程序需要通过BIOS提供的int 0x13中断向量所指向的中断服务程序
! =============================================================================

load_setup:
	mov	dx,#0x0000		    ! drive 0, head 0  设置软盘驱动器号为0，磁头号为0，DL为驱动器号，DH为磁头号
	mov	cx,#0x0002		    ! sector 2, track 0  读取的扇区号为2，磁道号为0
	mov	bx,#0x0200		    ! address = 512, in INITSEG  读取到内存的地址为512，在INITSEG 0x90000中，扩展段寄存器ES:基址寄存器BX ->内存缓冲区 ES:BX=0x90000:0x00200 目标地址 0x90200
	mov	ax,#0x0200+SETUPLEN	! service 2, nr of sectors 要读取的扇区数量为SETUPLEN（4），服务号为2  AH表示BIOS应该读扇区，AL表示读取4个
	int	0x13			    ! read it 调用BIOS中断0x13，读取软盘数据
	jnc	ok_load_setup		! ok - continue 如果读取成功，跳转到ok_load_setup标签
	mov	dx,#0x0000
	mov	ax,#0x0000		    ! reset the diskette 如果读取失败，重置软盘驱动器
	int	0x13
	j	load_setup          ! 重新读取

ok_load_setup:

! Get disk drive parameters, specifically nr of sectors/track 
! =============================================================================
! 用来获取磁盘驱动器的参数，其中包括磁道上每个扇区的数量（也就是每个扇区的大小）获取后将其存储在变量sectors中
! =============================================================================
	mov	dl,#0x00        ! 将寄存器dl设置为驱动器0。
	mov	ax,#0x0800		! AH=8 is get drive parameters 将寄存器ax设置为获取驱动器参数的服务号码8。
	int	0x13            ! 调用BIOS中断0x13，将获取到的驱动器参数存储在寄存器cx中
	mov	ch,#0x00        ! 将寄存器ch设置为0，以便从cx中提取扇区数。
	seg cs              ! 将段寄存器es设置为当前代码段
	mov	sectors,cx      ! 将cx中存储的扇区数存储在变量sectors中
	mov	ax,#INITSEG     ! 将寄存器ax设置为指向初始化段的地址
	mov	es,ax           ! 将段寄存器es设置为ax中存储的地址

! Print some inane message  
! =============================================================================
! 打印一些信息
! =============================================================================

	mov	ah,#0x03		! read cursor pos   将0x03赋值给寄存器AH，用于后续的int 0x10中断调用
	xor	bh,bh           ! 将寄存器BH清零，表示显示器的页码为0。
	int	0x10            ! 调用显示器的BIOS中断，将光标的位置读入到寄存器CX和DX中
	
	mov	cx,#24          ! 将寄存器CX赋值为24，表示接下来要输出的字符串的长度
	mov	bx,#0x0007		! page 0, attribute 7 (normal) 将寄存器BX赋值为0x0007，表示接下来要输出的字符串的属性为白底黑字
	mov	bp,#msg1        ! 将字符串msg1的地址赋值给寄存器BP，表示接下来要输出的字符串的存放位置
	mov	ax,#0x1301		! write string, move cursor 将0x1301赋值给寄存器AX，表示接下来要进行字符串的输出并移动光标
	int	0x10            ! 调用显示器的BIOS中断，将字符串输出到屏幕上，并移动光标到下一个要输出的位置

! ok, we've written the message, now
! we want to load the system (at 0x10000)
! =============================================================================
! 好的，我们已经输出了消息，现在我们要加载系统（位于地址 0x10000）
! 在启动操作系统时，需要把引导扇区的内容读取到内存中，才能执行引导程序。
! 这里的代码就是实现了这个功能，将软盘中的内容读取到内存中的 0x010000 地址处，以便后续执行引导程序。
! 而在读取完软盘中的内容后，需要关闭软驱的电机，以免造成硬件损坏。
! =============================================================================

	mov	ax,#SYSSEG        ! 把值 SYSSEG（存放在代码中的常量）装载到寄存器 ax 中。
	mov	es,ax		      ! segment of 0x010000 把 ax 中的值装载到段寄存器 es 中，将 es 寄存器设置为 SYSSEG（0x1000）所在的段。
	call	read_it       ! 调用一个函数 read_it，这个函数的作用是读取软盘中第一个扇区的内容，并把读取到的内容存放到内存中的 0x010000 地址处
	call	kill_motor    ! 调用一个函数 kill_motor，这个函数的作用是关闭软驱的电机，以保护软驱和磁盘

! After that we check which root-device to use. If the device is
! defined (!= 0), nothing is done and the given device is used.
! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
! on the number of sectors that the BIOS reports currently.
! =============================================================================
! 在这之后，我们会检查使用哪个根设备。如果设备已经被定义（!=0），则不做任何处理并使用给定的设备。
! 否则，根据BIOS当前报告的扇区数目，选择/dev/PS0（2,28）或/dev/at0（2,8）中的一个作为根设备。
! 在引导程序执行期间，需要确定根文件系统所在的设备。如果用户已经明确指定根设备，则直接使用该设备。
! 否则，引导程序会通过BIOS中断来检测可用的设备，并根据这些设备的参数来选择根设备。
! =============================================================================

	seg cs                                     ! 将代码段的段地址加载到 ds 寄存器中，以便访问数据段中的变量
	mov	ax,root_dev                            ! 将存储根文件系统所在设备号的变量 root_dev 加载到 ax 寄存器中
	cmp	ax,#0                                  ! 比较 ax 寄存器中的值是否为 0，即检查 root_dev 变量是否已设置
	jne	root_defined                           ! 如果 root_dev 不为 0，则跳转到标签 root_defined，表示已经定义了根文件系统所在设备
	seg cs                                     
	mov	bx,sectors                             ! 将存储扇区数的变量 sectors 加载到 bx 寄存器中
	mov	ax,#0x0208		! /dev/ps0 - 1.2Mb     ! 将 ax 寄存器设置为设备号为 /dev/ps0 的软盘，并将扇区数设置为 15。软盘是一种常见的启动介质
	cmp	bx,#15                                 ! 比较 bx 寄存器中的值是否等于 15，如果是，则跳转到 root_defined 标签
	je	root_defined
	mov	ax,#0x021c		! /dev/PS0 - 1.44Mb    ! 将 ax 寄存器设置为设备号为 /dev/PS0 的软盘，并将扇区数设置为 18，这也是软盘的常见格式
	cmp	bx,#18                                 ! 比较 bx 寄存器中的值是否等于 18，如果是，则跳转到 root_defined 标签
	je	root_defined
undef_root:
	jmp undef_root                             ! 死循环
root_defined:
	seg cs                                     ! 一种内存寻址方式，表示使用当前代码所在的代码段来寻址内存。
	mov	root_dev,ax                            !将 ax 的值赋给 root_dev 变量

! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:
! =============================================================================
! 在加载完所有内容后，我们跳转到直接加载在引导块后面的安装程序。
! 在描述内核启动过程中的一步，即在加载完内核镜像和安装程序后，通过跳转到安装程序的入口点，开始执行安装程序的初始化工作，如初始化内存、初始化设备等
! =============================================================================

	jmpi	0,SETUPSEG                         ! 无条件跳转到地址 SETUPSEG（0x9020）。这个跳转的目的是继续执行后面的代码。

! This routine loads the system at address 0x10000, making sure
! no 64kB boundaries are crossed. We try to load it as fast as
! possible, loading whole tracks whenever we can.
!
! in:	es - starting address segment (normally 0x1000)
!
! =============================================================================
! 这个过程将系统system模块加载到0x10000地址处，确保不跨越64KB边界。我们尽可能快地加载它，尽可能整个磁道一次性加载。
! 输入参数：es - 起始地址段（通常为0x1000）。
! 这段话描述了安装程序的一部分，即它的加载系统的功能。安装程序在这里会将内核镜像加载到内存的0x10000地址处，并保证不会跨越64KB边界，以避免在运行时产生不可预期的错误。
! 同时，安装程序会尝试尽可能快地加载整个磁道，以减少磁盘IO操作次数，提高加载效率。
! 输入参数es表示起始地址段，通常为0x1000，表示内核镜像加载到内存的段地址
! =============================================================================
sread:	.word 1+SETUPLEN	! sectors read of current track  当前磁道读取的扇区数  初始值为5
head:	.word 0			    ! current head                   当前磁头的位置       初始值为0
track:	.word 0			    ! current track                  当前磁道的位置       初始值为0

read_it:
	mov ax,es
	test ax,#0x0fff         ! 检查es是否位于0x0fff的位置，也就是检查是否位于64kb边界
die:	jne die			    ! es must be at 64kB boundary 若不符合则进行死循环（要保证内存对齐）
	xor bx,bx		        ! bx is starting address within segment BX=0，段内偏移初始为0
rp_read:
	mov ax,es               ! 将es的值加载到ax寄存器中
	cmp ax,#ENDSEG		    ! have we loaded all yet?是否全部加载完毕？ 比较当前段是否达到加载结束段（ENDSEG）
	jb ok1_read             ! 若未完成（AX < ENDSEG），继续读取
	ret
ok1_read:
	seg cs                  ! 使用 CS 段前缀（访问代码段中的变量）
	mov ax,sectors          ! AX = 当前磁道总扇区数
	sub ax,sread            ! AX -= 当前磁道已读扇区数（剩余扇区数）
	mov cx,ax               ! CX = 剩余可读扇区数
	shl cx,#9               ! CX *= 512（转换为字节数）
	add cx,bx               ! CX += BX（当前段内已用空间）
	jnc ok2_read            ! 若无进位（未超出64KB），继续读取
	je ok2_read             ! 若正好等于64KB，也继续
	xor ax,ax               ! 计算剩余空间：AX = 0 - BX
	sub ax,bx
	shr ax,#9               ! AX = 剩余空间 / 512（转换为剩余扇区数）
ok2_read:
	call read_track			! 调用读取磁盘磁道的子程序
	mov cx,ax				! 将AX（实际读取的扇区数）暂存到CX
	add ax,sread			! AX += 当前磁道已读扇区数（总共已读扇区数） 内存操作：读取sread的值，加上ax的值，再将结果写入ax
	seg cs					! 代码段前缀，强制使用代码段中的变量
	cmp ax,sectors			! 比较已读总数和每磁道总扇区数（sectors在代码段），读取CS段中的sectors变量
	jne ok3_read			! 如果未读满整个磁道，跳转到ok3_read
	mov ax,#1
	sub ax,head
	jne ok4_read
	inc track
ok4_read:
	mov head,ax
	xor ax,ax
ok3_read:
	mov sread,ax			! 保存已读总数到sread变量
	shl cx,#9				! 乘以512，转换为字节数，将读取的扇区数转换为字节数
	add bx,cx				! 为下一次读取做准备，将当前段内已用空间加上读取的字节数，得到下一次读取的起始地址，推进缓冲区指针
	jnc rp_read				! 无进位，继续读取
	mov ax,es				! 有进位，段地址加1，继续读取，处理跨越64KB边界的情况
	add ax,#0x1000			! 0x1000 = 64KB，增加ES段地址，跨越64KB边界，每次读取一个64KB的内存块
	mov es,ax				! 保存新的段地址到ES寄存器
	xor bx,bx				! 重置段内偏移，bx=0，为下一次读取做准备，推进缓冲区指针
	jmp rp_read				! 无条件跳转到rp_read，继续读取下一扇区

! 读取磁道
read_track:
	push ax                     ! 保存AX寄存器
	push bx                     ! 保存BX寄存器
	push cx						! 保存CX寄存器
	push dx                     ! 保存DX寄存器
	mov dx,track				! 读取磁道号 内存操作：DX = track 初始值为0
	mov cx,sread				! 读取扇区数 内存操作：CX = sread 初始值为5
	inc cx						! 读取扇区数加1，因为第一个扇区是引导扇区，不是内核镜像，寄存器变化：CX = 6
	mov ch,dl					! 读取磁头号 内存操作：CH = DL
	mov dx,head					! 读取磁头号 内存操作：DX = head 初始值为0
	mov dh,dl					! 读取磁头号 内存操作：DH = DL
	mov dl,#0					! 读取驱动器号 内存操作：DL = 0
	and dx,#0x0100				! 读取磁头号 内存操作：DX = 0
	mov ah,#2					! BIOS读取扇区功能号，AH = 2
	int 0x13					! 调用BIOS磁盘服务，根据AH的值，执行读取扇区功能，读取磁道，内存操作，将数据读取到ES:BX指向的内存地址
	jc bad_rt					! 若读取失败，跳转到bad_rt，终止程序
	pop dx						! 恢复寄存器
	pop cx
	pop bx
	pop ax
	ret
bad_rt:	mov ax,#0
	mov dx,#0
	int 0x13
	pop dx
	pop cx
	pop bx
	pop ax
	jmp read_track

/*
 * This procedure turns off the floppy drive motor, so
 * that we enter the kernel in a known state, and
 * don't have to worry about it later.
 * =======================================================
 * 该操作会关闭软盘驱动器马达，确保内核加载时处于可控的初始化状态，从而避免后续硬件状态不可预测的问题。
 * 在Linux 0.11的启动过程中，BIOS会将引导程序bootsect.s从软盘第一个扇区加载到内存0x7C00处。此时软盘驱动器的马达可能仍在运转,软盘磁头未复位可能引发错误
 * =======================================================
 */
kill_motor:
	push dx
	mov dx,#0x3f2
	mov al,#0
	outb
	pop dx
	ret

sectors:
	.word 0          ! sectors变量初始值为0

msg1:
	.byte 13,10
	.ascii "Loading system ..."
	.byte 13,10,13,10

.org 508
root_dev:
	.word ROOT_DEV
boot_flag:
	.word 0xAA55

.text
endtext:
.data
enddata:
.bss
endbss:
